//Copyright (c) 2006-2007 Microsoft Corporation.  All rights reserved
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml.Serialization;

using InvocationFramework.Common;
using InvocationFramework.Common.Exceptions;
using InvocationFramework.Common.Resources;

namespace InvocationFramework.Adapters.Pipeline.Mock
{
    /// <summary>
    /// Clase para manejar el cache de ejecuciones simuladas
    /// </summary>
    /// <author>Rodolfo Finochietti 24/05/2006 12.32</author>
    internal class ResultCache
    {
        #region Factory

        /// <summary>
        /// Singleton para el cache
        /// </summary>
        public static readonly ResultCache Instance = new ResultCache( );
        
        #endregion

        #region Constants

        /// <summary>
        /// Separado para la clave de la entrada de cache
        /// </summary>
        private const string CacheKeySeparator = "|";

        #endregion
        
        #region Private vars

        /// <summary>
        /// Guarda las entradas de cache en memoria
        /// </summary>
        private Dictionary<int, ResultCacheEntry> _cache = new Dictionary<int, ResultCacheEntry>( );

        /// <summary>
        /// Variable para sincronizar el acceso al archivo de cache
        /// </summary>
        private object syncRoot = new object( );

        #endregion
        
        #region Constructors

        /// <summary>
        /// Constructor
        /// </summary>
        private ResultCache( ) { }

        #endregion
        
        #region Properties

        /// <summary>
        /// Variable privada para el manejo de la propiedad
        /// </summary>
        private string _cacheFile;

        /// <summary>
        /// Ubicacion del archivo de cache
        /// </summary>
        public string CacheFile
        {
            set
            {
                _cacheFile = value;
            }
        }

        /// <summary>
        /// Variable privada para el manejo de la propiedad
        /// </summary>
        private bool _mockMode;

        /// <summary>
        /// Indica si esta habilitado el modo de ejecucion simulado
        /// </summary>
        public bool MockMode
        {
            set
            {
                _mockMode = value;
            }
        }
        
        /// <summary>
        /// Permite obtener una entrada del cache
        /// </summary>
        /// <param name="executionInformation">Ejecucion de la que se desea obtener informacion de el cache</param>
        /// <returns>La entrada si existe, de lo contrario null</returns>
        /// <remarks>
        /// Si no esta habilitada la ejecucion simulada (<see cref="_mockMode"/> siempre se devuelve null
        /// </remarks>
        public ResultCacheEntry this[ InvocationInformation executionInformation ]
        {
            get
            {
                return Get( executionInformation );
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Permite obtener una entrada del cache
        /// </summary>
        /// <param name="executionInformation">Ejecucion de la que se desea obtener informacion de el cache</param>
        /// <returns>La entrada si existe, de lo contrario null</returns>
        /// <remarks>
        /// Si no esta habilitada la ejecucion simulada (<see cref="MockMode"/> siempre se devuelve null
        /// </remarks>
        public ResultCacheEntry Get( InvocationInformation executionInformation )
        {
            //Solo si estoy en un ejecucion simulada utilizo el cache
            if ( !_mockMode )
                return null;

            int hashedKey = GetHashedKey( executionInformation );

            if ( _cache.ContainsKey( hashedKey ) )
                return _cache[ hashedKey ];
            else
                throw new InvocationFrameworkException( Texts.MockFilterResultNotFound );
        }

        /// <summary>
        /// Agrega o actualiza una entrada en cache
        /// </summary>
        /// <param name="executionInformation">Ejecucion que se desea agregar al cache</param>
        public void Add( InvocationInformation executionInformation )
        {
            //Si estoy en un ejecucion simulada no actualizo el cache
            if ( _mockMode )
                return;

            int hashedKey = GetHashedKey( executionInformation );

            if ( !_cache.ContainsKey( hashedKey ) )
                _cache.Add( hashedKey, new ResultCacheEntry( hashedKey, executionInformation.Result ) );
            else
                _cache[ hashedKey ] = new ResultCacheEntry( hashedKey, executionInformation.Result );

            //No es muy optimo grabar le archivo cada vez que se actualiza un entrada, pero como el objetivo del filtro
            //es mas prolijo de hacerlo de esta manera
            Save( );
        }

        /// <summary>
        /// Cargar en memoria el archivo de cache (<see cref="CacheFile"/>)
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes" )]
        public void Load( )
        {
            lock ( syncRoot )
            {
                TextReader reader = null;
                try
                {
                    if ( File.Exists( _cacheFile ) )
                    {
                        reader = new StreamReader( _cacheFile );
                        XmlSerializer serializer = new XmlSerializer( typeof( ResultCacheEntry[] ) );
                        ResultCacheEntry[] cacheEntrys = ( ResultCacheEntry[] )serializer.Deserialize( reader );
                        reader.Close( );

                        _cache.Clear( );

                        foreach ( ResultCacheEntry entry in cacheEntrys )
                            _cache.Add( entry.HashedKey, entry );
                    }
                }
                catch //Si se produce un error elimino el archivo por que asumo que esta corrupto
                {
                    if ( reader != null )
                        reader.Close( );

                    //Si el archivo existe lo elimino para empezar de nuevo
                    if ( File.Exists( _cacheFile ) )
                        File.Delete( _cacheFile );
                }
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Graba el archivo de cache
        /// </summary>
        private void Save( )
        {
            ResultCacheEntry[] cacheEntrys = new ResultCacheEntry[ _cache.Count ];

            int i = 0;
            foreach ( KeyValuePair<int, ResultCacheEntry> kvp in _cache )
            {
                cacheEntrys[ i ] = kvp.Value;
                i++;
            }

            lock ( syncRoot )
            {
                TextWriter writer = new StreamWriter( _cacheFile );
                XmlSerializer serializer = new XmlSerializer( typeof( ResultCacheEntry[] ) );
                serializer.Serialize( writer, cacheEntrys );
                writer.Close( );
            }
        }

        /// <summary>
        /// Devuelve un string con la clave del cache para una ejecucion
        /// </summary>
        /// <param name="executionInformation">Informacion de ejecucion para generar la clave</param>
        /// <returns>Clave generada a partir de una ejecucion</returns>
        private static int GetHashedKey( InvocationInformation executionInformation )
        {
            StringBuilder sb = new StringBuilder( );

            sb.Append( executionInformation.ServiceName );
            sb.Append( CacheKeySeparator );
            sb.Append( executionInformation.OperationName );

            sb.Append( CacheKeySeparator );

            //Si hay parametros los serializo a xml para luego calcular el hash code de todo
            if ( executionInformation.Parameters != null && executionInformation.Parameters.Length > 0 )
            {
                StringWriter writer = new StringWriter( sb, CultureInfo.InvariantCulture );
                XmlSerializer serializer = new XmlSerializer( typeof( object[] ) );
                serializer.Serialize( writer, executionInformation.Parameters );
                writer.Close( );
            }

            return sb.ToString( ).GetHashCode( );
        }

        #endregion
    }
}